/*
 * Copyright (c) 2023, mdf Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-10-20     MacRsh       First version
 */

#include "uart.h"

static int mdf_uart_open(struct mdf_dev *dev)
{
    struct mdf_uart *uart = (struct mdf_uart *)dev;
    struct mdf_uart_ops *ops = (struct mdf_uart_ops *)dev->drv->ops;

    /* Reset the fifo */
    mdf_ringbuf_reset(&uart->rd_fifo);
    mdf_ringbuf_reset(&uart->wr_fifo);

    /* Configure the uart */
    return ops->configure(uart, &uart->config);
}

static int mdf_uart_close(struct mdf_dev *dev)
{
    struct mdf_uart *uart = (struct mdf_uart *)dev;
    struct mdf_uart_ops *ops = (struct mdf_uart_ops *)dev->drv->ops;
    struct mdf_uart_config close_config = {0};

    /* Configure the uart */
    return ops->configure(uart, &close_config);
}

ssize_t mdf_uart_read(struct mdf_dev *dev, int off, void *buf, size_t size, uint32_t sync_or_async)
{
    struct mdf_uart *uart = (struct mdf_uart *)dev;
    struct mdf_uart_ops *ops = (struct mdf_uart_ops *)dev->drv->ops;

    if (mdf_ringbuf_get_buffer_size(&uart->rd_fifo) == 0)
    {
        return (ssize_t)ops->read(uart, buf, size);
    } else
    {
        return (ssize_t)mdf_ringbuf_read(&uart->rd_fifo, buf, size);
    }
}

ssize_t mdf_uart_write(struct mdf_dev *dev, int off, const void *buf, size_t size, uint32_t sync_or_async)
{
    struct mdf_uart *uart = (struct mdf_uart *)dev;
    struct mdf_uart_ops *ops = (struct mdf_uart_ops *)dev->drv->ops;

    if (sync_or_async == 0)
    {
        return (ssize_t)ops->write(uart, buf, size);
    } else
    {
        ssize_t ret = (ssize_t)mdf_ringbuf_write(&uart->rd_fifo, buf, size);

        /* Start sending */
        ops->start_tx(uart);
        return ret;
    }
}

int mdf_uart_ioctl(struct mdf_dev *dev, int off, int cmd, void *args)
{
    struct mdf_uart *uart = (struct mdf_uart *)dev;
    struct mdf_uart_ops *ops = (struct mdf_uart_ops *)dev->drv->ops;

    switch (cmd)
    {
        case MDF_IOCTL_SET_CONFIG:
        {
            if (args != NULL)
            {
                struct mdf_uart_config *config = (struct mdf_uart_config *)args;
                int ret = ops->configure(uart, config);
                if (ret == MDF_EOK)
                {
                    uart->config = *config;
                }
                return ret;
            }
            return MDF_EINVAL;
        }
        case MDF_IOCTL_SET_RD_BUFSZ:
        {
            if (args != NULL)
            {
                return mdf_ringbuf_allocate_buffer(&uart->rd_fifo, *(size_t *)args);
            }
            return MDF_EINVAL;
        }
        case MDF_IOCTL_SET_WR_BUFSZ:
        {
            if (args != NULL)
            {
                return mdf_ringbuf_allocate_buffer(&uart->wr_fifo, *(size_t *)args);
            }
            return MDF_EINVAL;
        }

        case MDF_IOCTL_GET_CONFIG:
        {
            if (args != NULL)
            {
                struct mdf_uart_config *config = (struct mdf_uart_config *)args;
                *config = uart->config;
                return MDF_EOK;
            }
            return MDF_EINVAL;
        }
        case MDF_IOCTL_GET_RD_BUFSZ:
        {
            if (args != NULL)
            {
                *(size_t *)args = mdf_ringbuf_get_buffer_size(&uart->rd_fifo);
                return MDF_EOK;
            }
            return MDF_EINVAL;
        }
        case MDF_IOCTL_GET_WR_BUFSZ:
        {
            if (args != NULL)
            {
                *(size_t *)args = mdf_ringbuf_get_buffer_size(&uart->wr_fifo);
                return MDF_EOK;
            }
            return MDF_EINVAL;
        }

        default:
        {
            return MDF_ENOTSUP;
        }
    }
}

size_t mdf_uart_isr(struct mdf_dev *dev, uint32_t event)
{
    struct mdf_uart *uart = (struct mdf_uart *)dev;
    struct mdf_uart_ops *ops = (struct mdf_uart_ops *)dev->drv->ops;

    switch (event)
    {
        case MDF_ISR_EVENT_RD_INTER:
        {
            uint8_t data = 0;
            ops->read(uart, &data, sizeof(data));
            mdf_ringbuf_push_force(&uart->rd_fifo, data);
            return mdf_ringbuf_get_data_size(&uart->rd_fifo);
        }
        case MDF_ISR_EVENT_WR_INTER:
        {
            uint8_t data = 0;
            if (mdf_ringbuf_pop(&uart->wr_fifo, &data) == sizeof(data))
            {
                ops->write(uart, &data, sizeof(data));
            } else
            {
                ops->stop_tx(uart);
            }
            return mdf_ringbuf_get_data_size(&uart->wr_fifo);
        }

        default:
        {
            return MDF_ENOTSUP;
        }
    }
}

int mdf_uart_register(struct mdf_uart *uart, const char *name, struct mdf_drv *drv)
{
    static struct mdf_dev_ops ops =
        {
            mdf_uart_open,
            mdf_uart_close,
            mdf_uart_read,
            mdf_uart_write,
            mdf_uart_ioctl,
            mdf_uart_isr
        };
    struct mdf_uart_config default_config = MDF_UART_CONFIG_DEFAULT;

    MDF_ASSERT(uart != NULL);
    MDF_ASSERT(name != NULL);
    MDF_ASSERT(drv != NULL);
    MDF_ASSERT(drv->ops != NULL);

    /* Initialize the fields */
    uart->config = default_config;
    mdf_ringbuf_init(&uart->rd_fifo, NULL, 0);
    mdf_ringbuf_init(&uart->wr_fifo, NULL, 0);

    return mdf_dev_register(&uart->dev, name, Mdf_Dev_Type_Uart, MDF_SFLAG_RDWR | MDF_SFLAG_NONBLOCK, &ops, drv);
}
